package club.jdiy.core.job.exec;

import club.jdiy.core.AppContext;
import club.jdiy.core.JDiyContext;
import club.jdiy.core.job.entity.JDiyJob;
import club.jdiy.utils.BeanUtils;
import club.jdiy.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * JDiy定时任务处理抽象类
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20210622
 */
@Slf4j
public abstract class JobHandler implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        JDiyJob job = new JDiyJob();
        BeanUtils.copy(context.getMergedJobDataMap().get(TASK_PROPERTIES), job);
        try {
            run(context, job);
        } catch (Throwable t) {
            t.printStackTrace();
            throw new HandlerException("目标任务{" + job.getJobExecute() + "}调用失败!", t.getCause());
        }
    }

    /**
     * 执行方法，由子类重载
     *
     * @param context 工作执行上下文对象
     * @param JDiyJob 系统计划任务
     * @throws Exception 执行过程中的异常
     */
    protected abstract void run(JobExecutionContext context, JDiyJob JDiyJob) throws Exception;

    /**
     * 执行方法
     *
     * @param job 系统任务
     */
    protected void invokeMethod(JDiyJob job) throws Exception {
        String execStr = job.getJobExecute();
        String beanName = getBeanName(execStr);
        String methodName = getMethodName(execStr);
        ParamMeta paramMeta = getMethodParams(execStr);

        invokeMethod(
                isValidClassName(beanName)
                        ? Class.forName(beanName).getDeclaredConstructor().newInstance()
                        : context.getApplicationContext().getBean(beanName)
                , methodName, paramMeta);
    }


    /**
     * 初步测试用户输入的任务执行代码是否可用.
     * 主要检测要调用的目标类方法的有效性，但不检查方法内部的业务异常（业务异常将由任务日志记录）。
     * 如果此test方法无异常抛出，表明测试通过。
     * @param context JDiy 的AppContext上下文
     * @param job 系统任务
     */
    public static void testJobExecute(JDiyContext context, JDiyJob job) throws Exception {
        String execStr = job.getJobExecute();
        String beanName = getBeanName(execStr);
        String methodName = getMethodName(execStr);
        ParamMeta paramMeta = getMethodParams(execStr);

        Object bean = isValidClassName(beanName)
                ? Class.forName(beanName).getDeclaredConstructor().newInstance()
                : context.getApplicationContext().getBean(beanName);

        Method m = paramMeta.size() > 0
                ? BeanUtils.getMethodIgnoreBasicTypes(bean.getClass(), methodName, paramMeta.getParamTypes())
                .orElse(bean.getClass().getMethod(methodName, paramMeta.getParamTypes()))//orElse里面的，100%会出异常，主要是为了方法上面的 throws
                : bean.getClass().getDeclaredMethod(methodName);
        log.trace("testJob passed. " + m);
    }

    /**
     * 调用任务方法
     *
     * @param bean       目标对象
     * @param methodName 方法名称
     * @param paramMeta  方法形参与值参
     */
    private static void invokeMethod(Object bean, String methodName, ParamMeta paramMeta)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        if (paramMeta.size() > 0) {
            Method method = BeanUtils.getMethodIgnoreBasicTypes(bean.getClass(), methodName, paramMeta.getParamTypes())
                    .orElse(bean.getClass().getMethod(methodName, paramMeta.getParamTypes()));//orElse里面的，100%会出异常，主要是为了方法上面的 throws
            method.invoke(bean, paramMeta.getParamValues());
        } else {
            Method method = bean.getClass().getDeclaredMethod(methodName);
            method.invoke(bean);
        }
    }

    /**
     * 校验是否为为class包名
     *
     * @param invokeTarget 名称
     * @return true是 false否
     */
    private static boolean isValidClassName(String invokeTarget) {
        return StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    /**
     * 获取bean名称
     *
     * @param invokeTarget 目标字符串
     * @return bean名称
     */
    private static String getBeanName(String invokeTarget) {
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringBeforeLast(beanName, ".");
    }

    /**
     * 获取bean方法
     *
     * @param invokeTarget 目标字符串
     * @return method方法
     */
    private static String getMethodName(String invokeTarget) {
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringAfterLast(methodName, ".");
    }

    /**
     * 获取method方法参数相关列表
     *
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    private static ParamMeta getMethodParams(String invokeTarget) {
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr)) {
            return new ParamMeta(0);
        }
        String[] methodParams = methodStr.split(",");
        ParamMeta paramMeta = new ParamMeta(methodParams.length);
        for (String methodParam : methodParams) {
            String str = StringUtils.trimToEmpty(methodParam);
            if (StringUtils.contains(str, "'")) paramMeta.put(String.class, StringUtils.replace(str, "'", ""));
            else if (StringUtils.contains(str, "\"")) paramMeta.put(String.class, StringUtils.replace(str, "\"", ""));
            else if (StringUtils.equalsIgnoreCase(str, "true") || StringUtils.equalsIgnoreCase(str, "false"))
                paramMeta.put(boolean.class, Boolean.parseBoolean(str));
            else if (StringUtils.endsWithIgnoreCase(str, "l"))
                paramMeta.put(long.class, Long.parseLong(str.substring(0, str.length() - 1)));
            else if (StringUtils.endsWithIgnoreCase(str, "d"))
                paramMeta.put(double.class, Double.parseDouble(str.substring(0, str.length() - 1)));
            else if (StringUtils.endsWithIgnoreCase(str, "f"))
                paramMeta.put(float.class, Float.parseFloat(str.substring(0, str.length() - 1)));
            else if (int_pattern.matcher(str).matches())
                paramMeta.put(int.class, Integer.parseInt(str.substring(0, str.length() - 1)));
            else
                paramMeta.put(String.class, str);
        }
        return paramMeta;
    }

    public static final String TASK_PROPERTIES = "jobMetaData";

    private static final Pattern int_pattern = Pattern.compile("\\d+");

    private static final class ParamMeta {
        private final List<Class<?>> types;
        private final List<Object> values;
        private final int size;

        public ParamMeta(int size) {
            this.size = size;
            this.types = new ArrayList<>(size);
            this.values = new ArrayList<>(size);
        }

        public ParamMeta put(Class<?> type, Object value) {
            this.types.add(type);
            this.values.add(value);
            return this;
        }

        public Object[] getParamValues() {
            return values.toArray();
        }

        public Class<?>[] getParamTypes() {
            return types.toArray(new Class<?>[0]);
        }

        public int size() {
            return size;
        }
    }

    @Resource
    private JDiyContext context;
}
